Skip to content

fix(agents): point misplaced generation kwargs at generate_content_config - #6837

Open
a2105z wants to merge 2 commits into
google:mainfrom
a2105z:fix/llm-agent-generation-kwargs
Open

fix(agents): point misplaced generation kwargs at generate_content_config#6837
a2105z wants to merge 2 commits into
google:mainfrom
a2105z:fix/llm-agent-generation-kwargs

Conversation

@a2105z

@a2105z a2105z commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

LlmAgent(temperature=0.1) (and other GenerateContentConfig knobs) now raises a ValueError that names generate_content_config=types.GenerateContentConfig(...) instead of failing with opaque extra_forbidden.

Reserved knobs that already have an agent field (system_instruction, response_schema) still redirect to instruction= / output_schema=. Unknown keys that are not config fields are left alone so a real typo stays extra_forbidden. CamelCase aliases (topP, maxOutputTokens) are included. tools is skipped because it is already an LlmAgent field.

This is option 2 from #6836, per review on this PR: do not promote generation settings as public kwargs.

Closes: #6836

Test plan

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

Unit Tests:

uv run python -m pytest tests/unittests/agents/test_llm_agent_fields.py tests/unittests/agents/test_llm_agent_error_messages.py -q
105 passed

Covers the original temperature= failure, multiple knobs named in one error, camelCase aliases, reserved-field redirects, typos remaining extra_forbidden, tools= not hijacked, and subclasses inheriting the validator.

Manual End-to-End (E2E) Tests:

from google.adk.agents import LlmAgent

LlmAgent(name='grader', temperature=0.1)
# ValueError: temperature is a GenerateContentConfig field. Pass
# generate_content_config=types.GenerateContentConfig(temperature=...) instead.

agent = LlmAgent(
    name='grader',
    generate_content_config=types.GenerateContentConfig(temperature=0.1),
)
assert agent.generate_content_config.temperature == 0.1

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

@google-cla

google-cla Bot commented Aug 20, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@a2105z
a2105z force-pushed the fix/llm-agent-generation-kwargs branch from 8fe8946 to 9c94f11 Compare August 20, 2026 20:54
@a2105z

a2105z commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@googlebot I fixed it.

@DeanChensj

Copy link
Copy Markdown
Collaborator

Hi @a2105z — thanks for picking this up so quickly, and for reading the actual failure mode rather than just papering over it. Two parts of this I want to keep regardless of where we land: deriving the field names from GenerateContentConfig.model_fields instead of hardcoding them, and redirecting system_instruction / response_schema to the LlmAgent arguments that own them with a message that says so. That second one is a genuinely nice touch and I hadn't seen it proposed anywhere.

That said, after going through #6836 I think we should take the reporter's option 2 — a clear error message — rather than option 1, and I'd rather tell you now than after another review round.

The reason is in the issue title: "no discoverable path to generate_content_config". What cost @CarSanoja a debugging session wasn't that temperature= is rejected, it's that the rejection points nowhere. An error that names the destination fixes that for every field at once:
temperature is a GenerateContentConfig field. Pass
generate_content_config=types.GenerateContentConfig(temperature=...) instead.

Accepting the kwargs solves it too, but it commits us to more than it looks like. It gives two spellings for the same setting, and every field google-genai adds becomes a decision about whether to promote it — a decision we can't easily revise later, because once these are accepted they're public API.

There's also a coverage asymmetry that's easy to miss. GenerateContentConfig has 35 fields today. Folding a subset means the ones left out still fail with the same opaque extra_forbidden, and arguably worse than before, because the user has now learned that generation settings work as kwargs. An error-message fix derived from model_fields covers all 35 and anything added later, with nothing to maintain.

If you're up for reworking it, what we'd take is roughly:

  • a model_validator(mode='before') that raises a helpful ValueError when an unknown key matches a GenerateContentConfig field, keeping your system_instruction / response_schema redirects as a special case
  • unknown keys that aren't config fields left alone, so a real typo still reads as extra_forbidden rather than a lecture about config
  • the short docstring paragraph from your PR showing the generate_content_config form — that was option 3 in the issue and worth keeping either way
    That should be substantially smaller than the current PR. If you'd rather not redo it, say so and I'll pick it up — you found the problem and the redirect idea, and that'll be credited either way.

Thanks again

@CarSanoja

Copy link
Copy Markdown

Following up on Dean's redirect, since I filed this one and had already signed off on the kwargs version.

To be clear about what my earlier test did and did not say: #6837 does fix the failure and I stand by those five results. Dean's objection is about API surface rather than correctness, and on that axis I think he is right — it is also the option I put in the issue. The title is "no discoverable path", and what cost me the session was not that temperature= is rejected: it is that the raw failure is literally the single word temperature. An error that names the destination fixes that for all 35 fields at once, where accepting a subset leaves the rest failing the same way, now against a learned expectation that generation settings work as kwargs.

I built Dean's spec against adk-python 2.7.0 to check it survives contact — a model_validator(mode="before") that fires only on keys unknown to LlmAgent which are also GenerateContentConfig fields, about 25 lines:

case result
temperature=0.2 error naming generate_content_config=types.GenerateContentConfig(temperature=...)
system_instruction= / response_schema= redirected to instruction / output_schema
temperature= and top_p= together both named in one error, not just the first
temperatur=0.2 (a real typo) untouched: plain extra_forbidden, no lecture
tools=[], generate_content_config=... construct normally
subclasses of LlmAgent inherit the validator

Two things from @a2105z's PR that should survive into the smaller shape:

  1. The system_instruction / response_schema redirect map. It is the best idea in the thread and it reads better than what I originally asked for.
  2. The camelCase alias map. All 35 config fields carry an alias, so topP= and maxOutputTokens= are as likely a first attempt as the snake_case spellings; without the alias lookup they fall back to a bare extra_forbidden and the fix misses exactly the users it is for.

One trap for whoever writes it: tools is the only name present on both LlmAgent and GenerateContentConfig (35 config fields against 33 agent fields today). Gating on "unknown to the agent" rather than "matches a config field" is what keeps tools working — worth a regression test so it cannot drift into hijacking it.

@a2105z happy to hand you the implementation and the edge-case tests so this stays your PR, or to open it myself if you would rather not redo it — whichever unblocks it faster. Either way I will re-test whatever lands before merge.

@a2105z

a2105z commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@DeanChensj @CarSanoja Thanks — reworked this to option 2 instead of folding the kwargs.

Pushed on this branch. The validator is now a model_validator(mode='before') that:

  • raises a ValueError naming generate_content_config=types.GenerateContentConfig(...) when an unknown key matches a GenerateContentConfig field (derived from model_fields, including camelCase aliases)
  • keeps the system_instructioninstruction and response_schemaoutput_schema redirects
  • leaves real typos (temperatur=) as extra_forbidden
  • gates on “unknown to LlmAgent” so tools= is not hijacked
  • names multiple misplaced fields in one error
  • is inherited by subclasses

Class docstring now shows the generate_content_config= form. Happy to tweak the wording if you want it closer to the issue title.

@CarSanoja kept this as my PR — your edge-case table is covered in test_llm_agent_error_messages.py. Would appreciate a re-test when you have a chance.

@a2105z a2105z changed the title fix(agents): accept generation kwargs on LlmAgent fix(agents): point misplaced generation kwargs at generate_content_config Aug 26, 2026
@CarSanoja

Copy link
Copy Markdown

Re-tested, as promised. This resolves the issue for me, and it is better than what I proposed.

Clean venv, Python 3.13, google-genai 2.20.0, branch at 14ac8d2. Your own suite: 20 passed. My table, all against the branch:

case result
temperature=0.2 temperature is a GenerateContentConfig field. Pass generate_content_config=types.GenerateContentConfig(temperature=...) instead.
topP=0.9 (camelCase) resolved and reported as top_p
maxOutputTokens=256 resolved and reported as max_output_tokens
system_instruction= must be set via LlmAgent.instruction
response_schema= must be set via LlmAgent.output_schema
temperature= + top_p= both named, and the suggested constructor carries both
temperatur=0.2 (a real typo) plain extra_forbidden — untouched
tools=[] constructs
generate_content_config=... constructs
generate_content_config= + instruction= constructs
subclass of LlmAgent inherits the validator
LlmAgent.model_validate({... "temperature": 0.2}) same error on the dict path
Agent(...) (the alias) same error
temperature=None still redirected, which is right

Two things you did that I had not thought of, and that I would keep:

  1. Reporting the canonical name for an alias. topP= fails with top_p is a GenerateContentConfig field, so the message teaches the spelling that will actually work rather than echoing the one that failed.
  2. The combined suggestion for several fields at once — one error that ends in GenerateContentConfig(temperature=..., top_p=...) is a copy-paste fix instead of two round trips.

One optional bit of polish, not a blocker, and I would understand leaving it: when a real typo and a config field arrive together (temperatur=0.2, top_k=5), the error names top_k and stays silent about temperatur. The user fixes the config field, re-runs, and only then meets the typo. Same for system_instruction= together with temperature=: the redirect is reported and the config field is not. If pydantic makes it easy to raise once with both kinds, it would fit the discoverability goal; if it does not, this is a strictly better message than what is on main today.

Thanks for reworking it and for the tests — test_llm_agent_error_messages.py covers the cases I care about, including the tools gate. From my side this is ready.

a2105z added 2 commits August 26, 2026 15:18
Users coming from google-genai pass temperature= (and similar knobs) on
LlmAgent, which failed with extra_forbidden and no pointer to
generate_content_config. Fold GenerateContentConfig fields into that
config, and point reserved fields at the LlmAgent argument that owns them.

Fixes google#6836
…nfig

Keep LlmAgent construction from accepting temperature= and other
GenerateContentConfig fields as public API. Raise a ValueError that names
generate_content_config instead, with reserved-field redirects and camelCase
aliases preserved. Report redirects, config fields, and extras in one error
when they arrive together.
@a2105z
a2105z force-pushed the fix/llm-agent-generation-kwargs branch from 14ac8d2 to 07349c5 Compare August 26, 2026 20:18
@a2105z

a2105z commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the optional polish: pydantic's extra_forbidden only runs if the before-validator returns, so a config-field ValueError would still swallow a typo. The validator now collects redirects, config-field names, and unknown extras and raises them in one ValueError when a config-field error is already firing:

  • temperatur=0.2, top_k=5 names top_k and Extra inputs are not permitted: temperatur
  • system_instruction= with temperature= reports the instruction= redirect and the generate_content_config path

A typo alone is still left to pydantic extra_forbidden.

@a2105z
a2105z force-pushed the fix/llm-agent-generation-kwargs branch from 07349c5 to 33277b5 Compare August 26, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LlmAgent rejects temperature= with extra_forbidden — no discoverable path to generate_content_config

3 participants